package org.fuys.coder.infrastructure.hook;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.fuys.coder.infrastructure.model.dto.UserPermissionDTO;
import org.fuys.coder.domain.config.service.impl.CoderSecurityConfig;
import org.fuys.coder.common.constants.RedisConstants;
import org.fuys.coder.common.entity.Pair;
import org.fuys.coder.common.exception.BusinessException;
import org.fuys.coder.common.holder.CategoryVOHolder;
import org.fuys.coder.common.holder.TokenExpireHolder;
import org.fuys.coder.infrastructure.util.LuaScriptLoader;
import org.fuys.coder.infrastructure.util.RedisUtil;
import org.fuys.coder.domain.act.lottery.model.vo.award.AwardTypeVO;
import org.fuys.coder.domain.act.lottery.service.strategy.ILotteryStrategyService;
import org.fuys.coder.domain.act.lottery.service.strategy.factory.LotteryStrategyFactory;
import org.fuys.coder.domain.auth.model.vo.permission.RoleTypeVO;
import org.fuys.coder.domain.auth.model.vo.token.TokenTypeVO;
import org.fuys.coder.domain.auth.service.holder.PermissionHolder;
import org.fuys.coder.domain.auth.service.vertify.permission.repo.IPermissionRepoServiceAggregation;
import org.fuys.coder.domain.category.model.vo.CategoryVO;
import org.fuys.coder.domain.category.service.ICategoryService;
import org.fuys.coder.infrastructure.dao.AwardTypeMapper;
import org.fuys.coder.infrastructure.model.po.AwardType;
import org.springframework.boot.CommandLineRunner;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @projectName: fuys-low-coder
 * @package: org.fuys.coder.common.hook
 * @className: DBToCacheHook
 * @author: WangJing
 * @description: 从数据库读取内容至缓存中 以应对不经常改变的数据
 * @date: 2024/7/5 22:38
 * @version: 1.0
 */
@Component
public class DBToCacheHook implements CommandLineRunner {
    //todo 需要解耦一下 将这些放入缓存的方法进行抽离 方便维护
    @Resource
    private ILotteryStrategyService lotteryStrategyService;

    @Resource
    private IPermissionRepoServiceAggregation permissionRepoServiceAggregation;

    @Resource
    private ICategoryService categoryService;

    @Resource
    private LuaScriptLoader luaScriptLoader;

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private CoderSecurityConfig coderSecurityConfig;

    @Resource
    private AwardTypeMapper awardTypeMapper;

    @Override
    public void run(String... args) throws Exception {
        cacheTokenExpire();
        uploadLuaScript();
        final List<AwardType> awardTypes = awardTypeMapper.selectList(new LambdaQueryWrapper<>());
        if(!ObjectUtils.isEmpty(awardTypes)){
            final Map<Integer, Pair<String, String>> collect = awardTypes.stream().collect(Collectors.toMap(AwardType::getAwardType
                    , awardType -> {
                        Pair<String, String> pair = new Pair<>();
                        pair.setLeft(awardType.getDescription());
                        pair.setRight(awardType.getStrategyName());
                        return pair;
                    }));
            AwardTypeVO.setTypeMap(collect);
        }

        LotteryStrategyFactory.initFactory(lotteryStrategyService.getAllStrategyInfo());
        final List<CategoryVO> allCategory = categoryService.getAllCategory();
        allCategory.forEach(CategoryVOHolder::putCategory);
        final List<UserPermissionDTO> allRoleDetail = permissionRepoServiceAggregation.getAllRoleDetail();
        //对于单体架构 使用Java层面的缓存已经足够 因为不需要进行数据同步
        //todo 优化redis获取权限 因为现在权限全部在map中
        List<UserPermissionDTO> commonList = new ArrayList<>();
        List<UserPermissionDTO> fixedList = new ArrayList<>();
        allRoleDetail.forEach((item) -> {
            final Integer roleId = item.getRoleId();
            if (roleId.equals(RoleTypeVO.ROOT.getRoleId()) || roleId.equals(RoleTypeVO.RESIDENTADMIN.getRoleId())) {
                fixedList.add(item);
            } else {
                commonList.add(item);
            }
        });
        PermissionHolder.initPermission(allRoleDetail);
        redisUtil.getRedisTemplate().executePipelined(new SessionCallback<Object>() {
                                                          @Override
                                                          public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
                                                              for (UserPermissionDTO userPermissionDTO : fixedList) {
                                                                  String pushKey = RedisConstants.REDIS_FIELD_USER + userPermissionDTO.getUserId() +
                                                                          RedisConstants.REDIS_SPLIT + RedisConstants.REDIS_USE_PERMISSION;
                                                                  operations.opsForValue().set((K) pushKey, (V) userPermissionDTO);
                                                              }
                                                              return null;
                                                          }
                                                      }
        );
    }

    private void uploadLuaScript() {
        Map<String, String> shaMap = new HashMap<>();
        final Map<String, String> scriptContentMap = luaScriptLoader.loadLuaScript();
        final Set<String> strings = scriptContentMap.keySet();
        for (String value : strings) {
//            DefaultRedisScript<Object> defaultRedisScript = new DefaultRedisScript<>();
//            defaultRedisScript.setScriptText(scriptContentMap.get(value));
//            defaultRedisScript.setResultType(Object.class);
            final DefaultRedisScript<String> defaultRedisScript = luaScriptLoader.getScript(value);
            final Object sha = redisUtil.getRedisTemplate().execute((RedisCallback<Object>) redisConnection -> {
                return redisConnection.scriptingCommands().scriptLoad(defaultRedisScript.getScriptAsString().getBytes(StandardCharsets.UTF_8));
            });
            if (ObjectUtils.isEmpty(sha)) {
                throw new BusinessException("lua脚本上传失败");
            }
            shaMap.put(value, sha.toString());
        }
        luaScriptLoader.setScriptMap(shaMap);
    }

    private void cacheTokenExpire(){
        TokenExpireHolder.expireMap.put(TokenTypeVO.SUBSTANCE_PUBLISH,coderSecurityConfig.getPublishTokenExpire().longValue());
        TokenExpireHolder.expireMap.put(TokenTypeVO.SUBSTANCE_THUMB,coderSecurityConfig.getThumbTokenExpire().longValue());
        TokenExpireHolder.expireMap.put(TokenTypeVO.SUBSTANCE_FAVORITES,coderSecurityConfig.getFavTokenExpire().longValue());
    }
}
